在各种系统中,可以笼统地把数据分为两方:一个是数据提供者,是服务方;另一个是数据访问者,是被服务方。数据提供者提供数据,数据访问者访问这些数据。需要注意的是,对于应用程序而言,到底是服务方还是被服务方,其身份并不是固定的,因为它有可能某一刻提供服务,下一刻又使用了别人的服务,或者既为其它程序提供数据,同时又访问其它程序的数据。

在前面章节里,使用Intent可以是向其它应用程序传递数据,这是一种”主动“的行为,即应用程序主动发起请求,通过Intent把数据传递给对方。但有的时候,应用程序希望能够访问其它应用的数据,例如访问联系人列表等,或者对数据进行一些操作,例如增、删、改、查等。那么对于其它应用程序而言,就是一种”被动”的行为。其它应用程序或者是直接响应这种请求,把数据回传给对方或执行操作,或者干脆把自己的数据文件开放给对方,让对方直接操作。显然后面这种方式是不安全的。

应用程序形成的SharedPreferences、File或数据库,一般而言只能供自己访问,并且从安全角度出发,让其它应用程序直接访问也是危险的。

那么在Android系统里面如何实现数据的共享呢?答案是ContentProvider、ContentResolver等。应用程序通过ContentProvider,提供了供外界程序对自身数据进行操纵的界面。

7.4.1.1 ContentProvider简介

ContentProvider必须实现如下方法:

  • boolean onCreate():用来对ContentProvider初始化。

  • Cursor query(Uri, String[], String, String[], String):进行数据查询。

  • Uri insert(Uri, ContentValues):将数据插入到ContentProvider中。

  • int update(Uri, ContentValues, String, String[]):更新ContentProvider中的数据。

  • int delete(Uri, String, String[]):删除ContentProvider中的数据。

  • String getType(Uri):返回ContentProvider中数据MIME类型。

7.4.1.2 Uri

1、Uri

Android系统用Uri来标识数据资源,格式如下:

content:\/\/authority\/<data_path>

Uri各部分介绍如下:

  • content:\/\/:协议名称,对于ContentProvider来说,协议为”content:\/\/”。

  • authority:授权者名字,用来确定系统中哪个ContentProvier提供资源。

  • data_path:数据路径,用来确定请求的数据集。如果ContentProvider提供的数据集唯一,则data_path可以省略。

系统提供了Uri类,该类有几个常用方法:

  • List<String>getPathSegments():解析路径,将data_path按照”\/”分隔符进行拆分,生成每个元素类型是String的List,注意每个元素不包含“\/”。例如如果Uri的值为”content:\/\/com.sample\/words\/id”,则调用getPathSegments()后返回的结果为:{“words”,”id”}。

2、UriMatcher

给定一个Uri后,怎么确定ContentProvider能够识别它呢?Android SDK提供了UriMatcher类,使用该类可以很方便地确定Uri的类型。

UriMatcher主要有两个方法。

  • oid addURI(String authority, String path, int code):向UriMatcher对象添加一个Uri,新生成的Uri为:content:\/\/authority\/path,表示UriMatcher对象能够是被该Uri。如果另外一个Uri同该Uri匹配,则返回code。这样我们就可以通过返回的code来区分不同的Uri。code应该是大于等于0的值。

  • int match(Uri uri):返回匹配结果(code)。根据UriMatcher对象中Uri的不同返回不同的匹配结果,如果Uri同UriMatcher对象中的任何Uri都不匹配,则返回NO_MATCH(-1)。

UriMatcher的使用方法一般分为两步。

首先使用addURI()方法向UriMatcher对象添加ContentProvider能够识别的Uri及对应的匹配结果(code)。

private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

static {
    uriMatcher.addURI(“com.sample”, “words”, 1);
    uriMatcher.addURI(“com.sample”, “words/#”, 2);
}

然后使用match()方法来判断传过来的Uri对应哪个匹配结果(code),根据code不同做不同的处理。

注意:在Uri的data_path部分中,使用#表示匹配数字,*表示匹配文本。例如对于”content:\/\/com.sample\/words\/#”来说,”content:\/\/com.sample\/words\/1”、”content:\/\/com.sample\/words\/23”等都同它匹配,对应的code都是2。

switch(uriMatcher.match(uri)){ 
   case 1: 
      …//处理code为1时对应的uri请求
      break;
   case 2: 
      …//处理code为2时对应的uri请求
      break;
}

3、ContentUris

此外Android SDK还提供了ContentUris。该类提供了如下方法:

  • Uri.BuilderappendId(Uri.Builder builder, long id):在路径后面添加id,即在路径后面添加”\/id”。

  • static longparseId(Uri contentUri):解析路径中的id。

  • static Uri withAppendedId(Uri contentUri, long id):在路径后面添加id,即在路径后面添加”\/id”。

举例如下:

Uri uri=Uri.parse(“content://com.sample/words”); 
Uri newuri=ContentUris.withAppendedId(Uri,3);//newuri结果为:content://com.sample/words/3  
long id=parseId(newuri);//id为3

7.4.1.3 ContentResolver

ContentResolver用来对请求的Uri进行解析,确定要访问的ContentProvider。

  • int delete(Uri url, String where, String[] selectionArgs):删除

  • Uri insert(Uri url, ContentValues values):增加

  • int update(Uri uri, ContentValues values, String where, String[] selectionArgs):更新

  • Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder):查询

  • Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder, CancellationSignal cancellationSignal):查询

  • void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork):通知注册方数据已经发生改变。

  • void notifyChange(Uri uri, ContentObserver observer)

用户通过Context提供的getConentResolver()方法来获得应用程序默认的ContentResolver。

7.4.1.4 数据共享示例

在此示例中,共有两个应用程序。一个是ContentProvider方,数据提供者;一个是使用ContentProvider方。这里我们仍然以单词本app为例。

1、开发ContentProvider

分为两个步骤,首先从ContentProvider派生出自己的类,然后对ContentProvider进行配置。

修改Words.java文件,添加识别的Uri对象、数据的MIME类型等。

public class Words {
   public static final String AUTHORITY = "cn.edu.bistu.cs.se.wordsprovider";//URI授权者 

   public Words() { }

   public static abstract class Word implements BaseColumns { 
      public static final String TABLE_NAME="words";//表名 
      public static final String COLUMN_NAME_WORD="word";//列:单词 
      public static final String COLUMN_NAME_MEANING="meaning";//列:单词含义
      public static final String COLUMN_NAME_SAMPLE="sample";//列:单词示例 

      //MIME类型  
      public static final String MIME_DIR_PREFIX = "vnd.android.cursor.dir";  
      public static final String MIME_ITEM_PREFIX = "vnd.android.cursor.item";  
      public static final String MINE_ITEM = "vnd.bistu.cs.se.word"; 

      public static final String MINE_TYPE_SINGLE = MIME_ITEM_PREFIX + "/" + MINE_ITEM; 
      public static final String MINE_TYPE_MULTIPLE = MIME_DIR_PREFIX + "/" + MINE_ITEM; 

      public static final String PATH_SINGLE = "word/#";//单条数据的路径 
      public static final String PATH_MULTIPLE = "word";//多条数据的路径

      //Content Uri
      public static final String CONTENT_URI_STRING = "content://" + AUTHORITY + "/" + PATH_MULTIPLE; 
      public static final Uri CONTENT_URI = Uri.parse(CONTENT_URI_STRING); 
   }
}

2、创建ContentProvider类

(1)在\/java\/packagename(项目中包名,示例中为en.edu.bistu.cs.se.databaseapp)上单击右键,依次选择”new”->”Other”->”Content Provider”,如图所示。

(2)系统弹出配置对话框(Android Studio存在bug,显示的是配置Activity)。如下图所示。其中选中Exported,即允许其它程序访问。

(3)Android给我们生成了一个从ContentProvider派生的类WordsProvider,同时在AndroidManifest.xml中做好了配置。先看配置文件,添加了<provoder…\/>子元素。

<provider  
    android:name=".WordsProvider"  
    android:authorities="wordsprovider.se.cs.bistu.edu.cn"  
    android:enabled="true"  
    android:exported="true" 
</provider>

简单对<provoder…\/>子元素各属性进行说明。

  • name属性值为自定义的WordsProvider,表示ContentProvider为此类。

  • authorities属性值为” wordsprovider.se.cs.bistu.edu.cn”,指定该ContentProvider对应的Uri。

  • exported属性值为true,表示允许其它应用程序调用,即对外开发数据访问接口。

(4)再看WordsProvider.java文件。Android Studio已经给我们搭好框架,用户只需要在相应方法内填写代码即可。

public class WordsProvider extends ContentProvider {
    //UriMathcher匹配结果码
    private static final int MULTIPLE_WORDS = 1; 
    private static final int SINGLE_WORD = 2;  

    WordsDBHelper mDbHelper;
    private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

    static {
        uriMatcher.addURI(Words.AUTHORITY, Words.Word.PATH_SINGLE, SINGLE_WORD); 
        uriMatcher.addURI(Words.AUTHORITY, Words.Word.PATH_MULTIPLE, MULTIPLE_WORDS); 
    }
    public WordsProvider() { }

    //删除数据 
    @Override 
    public int delete(Uri uri, String selection, String[] selectionArgs) { 
        SQLiteDatabase db = mDbHelper.getReadableDatabase();  
        int count = 0;
        switch (uriMatcher.match(uri)) {
            case MULTIPLE_WORDS: 
                count = db.delete(Words.Word.TABLE_NAME, selection, selectionArgs);
            break;
            case SINGLE_WORD:
                String whereClause=Words.Word._ID+"="+uri.getPathSegments().get(1); 
                count = db.delete(Words.Word.TABLE_NAME, whereClause, selectionArgs);
            break; 
            default:
                throw new IllegalArgumentException("Unkonwn Uri:" + uri); 
        } 
        //通知ContentResolver,数据已经发生改变 
        getContext().getContentResolver().notifyChange(uri, null); 
        return count; 
    }

    //返回Uri对应的数据的MIME类型
    @Override 
    public String getType(Uri uri) { 
        switch (uriMatcher.match(uri)) { 
            case MULTIPLE_WORDS://多条数据记录 
                return Words.Word.MINE_TYPE_MULTIPLE; 
            case SINGLE_WORD://单条数据记录 
                return Words.Word.MINE_TYPE_SINGLE; 
            default: 
                throw new IllegalArgumentException("Unkonwn Uri:" + uri);
        }
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        SQLiteDatabase db = mDbHelper.getReadableDatabase(); 
        long id = db.insert(Words.Word.TABLE_NAME, null, values); 
        if ( id >0 ){ 
            //在已有的Uri后面添加id 
            Uri newUri = ContentUris.withAppendedId(Words.Word.CONTENT_URI, id); 
            getContext().getContentResolver().notifyChange(newUri, null); 
            return newUri; 
        }
        throw new SQLException("Failed to insert row into " + uri); 
    }

    @Override
    public boolean onCreate() { 
        //创建SQLiteOpenHelper对象 
        mDbHelper = new WordsDBHelper(this.getContext());
        return true;
    }    

    @Override 
    public Cursor query(Uri uri, String[] projection, String selection,  
        String[] selectionArgs, String sortOrder) { 
             SQLiteDatabase db = mDbHelper.getReadableDatabase(); 
             SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); 
             qb.setTables(Words.Word.TABLE_NAME);
             switch (uriMatcher.match(uri)) { 
                  case MULTIPLE_WORDS:
                      return db.query(Words.Word.TABLE_NAME, projection, selection, 
                          selectionArgs, null, null, sortOrder); 
                  case SINGLE_WORD:  
                      qb.appendWhere(Words.Word._ID + "=" + uri.getPathSegments().get(1));
                      return qb.query(db, projection, selection, selectionArgs, null, null, sortOrder);
                  default: 
                      throw new IllegalArgumentException("Unkonwn Uri:" + uri); 
             }
        }
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection,  String[] selectionArgs) {
        SQLiteDatabase db = mDbHelper.getReadableDatabase();
        int count = 0; 
        switch (uriMatcher.match(uri)) { 
            case MULTIPLE_WORDS: 
                count = db.update(Words.Word.TABLE_NAME, values, selection, selectionArgs);
                break; 
            case SINGLE_WORD: 
                String segment = uri.getPathSegments().get(1); 
                count = db.update(Words.Word.TABLE_NAME, values, Words.Word._ID+"="+segment, selectionArgs); 
                break; 
            default:
                throw new IllegalArgumentException("Unkonwn Uri:" + uri); 
        }    

        //通知ContentResolver,数据已经发生改变
        getContext().getContentResolver().notifyChange(uri, null); 
        return count; 
    }
}
}

3、使用ContentProvider

另建一个程序AccessWordsApp。为方便起见,该程序比较简单,界面上有几个按钮,分别是“全部”、“增加”、“删除”、“全部删除”、“更新”和查询,在此不给出布局文件。当按下不同的按钮时,通过ContentResolver调用ContentProvider对象来执行不同的操作,从而实现数据共享和操控。

在这里,AccessWordsApp并不知道具体是数据存放在哪里,是什么样的格式,由哪一个应用程序处理的,它仅知道发出一个Uri,在Uri中包括要操作的数据的路径信息,然后交由ContentResolver处理(insert、delete、update、query等)即可。从这里可以看出,这大大减少了应用程序之间的耦合度,提高了程序模块化程度,降低了编程难度。但是作为编程人员,一定要知道背后Android系统做了大量工作。

程序中源代码仅有一个MainActivity.java,其主要框架如下:

public class MainActivity extends AppCompatActivity {
    private static final String TAG="MyWordsTag";
    private ContentResolver resolver; 

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        …//其它代码
        resolver = this.getContentResolver(); 

        //得到按钮
        Button buttonAll=(Button)findViewById(R.id.buttonAll); 
        Button buttonInsert=(Button)findViewById(R.id.buttonInsert); 
        Button buttonDelete=(Button)findViewById(R.id.buttonDelete); 
        Button buttonDeleteAll=(Button)findViewById(R.id.buttonDeleteAll); 
        Button buttonUpdate=(Button)findViewById(R.id.buttonUpdate); 
        Button buttonQuery=(Button)findViewById(R.id.buttonQuery);

        //为每个按钮设置监听器
        buttonAll.setOnClickListener(…); 
        buttonInsert.setOnClickListener(…);
        buttonDelete.setOnClickListener(…);
        buttonDeleteAll.setOnClickListener(…);
        buttonUpdate.setOnClickListener(…);
    }
     …//其它代码
}

“全部”按钮的监听器代码:

bttonAll.setOnClickListener(new View.OnClickListener() { 
    @Override
    public void onClick(View view) { 
        Cursor cursor = resolver.query(Words.Word.CONTENT_URI, new String[] { Words.Word._ID, 
           Words.Word.COLUMN_NAME_WORD,Words.Word.COLUMN_NAME_MEANING,Words.Word.COLUMN_NAME_SAMPLE}, 
               null, null, null);
           if (cursor == null){
           Toast.makeText(MainActivity.this,"没有找到记录",Toast.LENGTH_LONG).show(); 
           return;
        } 

        //找到记录,这里简单起见,使用Log输出 
        String msg = "";
        if (cursor.moveToFirst()){ 
            do{ 
                msg += "ID:" + cursor.getInt(cursor.getColumnIndex(Words.Word._ID)) + ","; 
                msg += "单词:" +  cursor.getString(cursor.getColumnIndex(Words.Word.COLUMN_NAME_WORD))+ ","; 
                msg += "含义:" + cursor.getInt(cursor.getColumnIndex(Words.Word.COLUMN_NAME_MEANING)) + ","; 
                msg += "示例" + cursor.getFloat(cursor.getColumnIndex(Words.Word.COLUMN_NAME_SAMPLE)) + "\n";
            } while(cursor.moveToNext()); 
        }
        Log.v(TAG,msg); 
    }
});

“增加”按钮的监听器代码:

buttonInsert.setOnClickListener(new View.OnClickListener() { 
   @Override  
   public void onClick(View view) { 
      String strWord="Banana";  
      String strMeaning="banana";  
      String strSample="This banana is very nice."; 
      ContentValues values = new ContentValues();  

      values.put(Words.Word.COLUMN_NAME_WORD, strWord);  
      values.put(Words.Word.COLUMN_NAME_MEANING, strMeaning);  
      values.put(Words.Word.COLUMN_NAME_SAMPLE, strSample); 

      Uri newUri = resolver.insert(Words.Word.CONTENT_URI, values); 
   }  
});

“删除”按钮的监听器代码:

buttonDelete.setOnClickListener(new View.OnClickListener() {  
    @Override  
    public void onClick(View view) {  
        String id="3";//简单起见,这里指定ID,用户可在程序中设置id的实际值  
        Uri uri = Uri.parse(Words.Word.CONTENT_URI_STRING + "/" + id);  
        int result = resolver.delete(uri, null, null);  
    }  
});

“全部删除”按钮的监听器代码:

buttonDeleteAll.setOnClickListener(new View.OnClickListener() {  
    @Override  
    public void onClick(View view) {  
        resolver.delete(Words.Word.CONTENT_URI, null, null);  
    }  
});

“更新”按钮的监听器代码:

buttonUpdate.setOnClickListener(new View.OnClickListener() {
    @Override  public void onClick(View view) {
        String id="3";  
        String strWord="Banana"; 
        String strMeaning="banana"; 
        String strSample="This banana is very nice."; 
        ContentValues values = new ContentValues(); 

        values.put(Words.Word.COLUMN_NAME_WORD, strWord); 
        values.put(Words.Word.COLUMN_NAME_MEANING, strMeaning);  
        values.put(Words.Word.COLUMN_NAME_SAMPLE, strSample);

        Uri uri = Uri.parse(Words.Word.CONTENT_URI_STRING + "/" + id);  
        int result = resolver.update(uri, values, null, null);  
    }  
});

“查询”按钮的监听器代码:

buttonQuery.setOnClickListener(new View.OnClickListener() {  
    @Override 
    public void onClick(View view) {  
        String id="3";  Uri uri = Uri.parse(Words.Word.CONTENT_URI_STRING + "/" + id);  
        Cursor cursor = resolver.query(Words.Word.CONTENT_URI,  
            new String[] { Words.Word._ID, Words.Word.COLUMN_NAME_WORD,  
            Words.Word.COLUMN_NAME_MEANING,Words.Word.COLUMN_NAME_SAMPLE}, 
            null, null, null);  
        if (cursor == null){ 
            Toast.makeText(MainActivity.this,"没有找到记录",Toast.LENGTH_LONG).show();  
            return; 
        }  
        //找到记录,这里简单起见,使用Log输出  
        String msg = "";  
        if (cursor.moveToFirst()){  
            do{  
                msg += "ID:" + cursor.getInt(cursor.getColumnIndex(Words.Word._ID)) + ",";  
                msg += "单词:" +  cursor.getString(cursor.getColumnIndex(Words.Word.COLUMN_NAME_WORD))+ ","; /b>; 
                msg += "含义:" +  cursor.getInt(cursor.getColumnIndex(Words.Word.COLUMN_NAME_MEANING)) + ","; b>; 
                msg += "示例" +  cursor.getFloat(cursor.getColumnIndex(Words.Word.COLUMN_NAME_SAMPLE)) + "\n";
            }while(cursor.moveToNext());  
        }  
        Log.v(TAG,msg);  
    }  
});

在本例中,修改、删除、查找等只能根据id来进行,请思考并修改根据Word字段内容来进行上述操作。

单击“全部”按钮时在Logcat中显示结果。

results matching ""

    No results matching ""